#ifndef GA_CPP
#define GA_CPP

#include "ga.h"

bool cmp(Ga::Snake *a, Ga::Snake *b) {
    return *a < *b;
}

Ga::Ga(int cell_num, int edge_num) : t(0) {
    omp_set_num_threads(THREADS);
    for (int i = 0; i < POPULATION_NUM; i++) {
        Snake *new_snake = new Snake(cell_num, edge_num);
        snakes.push_back(new_snake);
    }
    evaluateAll();
}

Ga::Ga(const char *filename) : t(0) {
    omp_set_num_threads(THREADS);
    for (int i = 0; true; i++) {
        char tmp[30];
        sprintf(tmp, "%s/%d.snk", filename, i);
        if (_access(tmp, 0) == -1)
            break;
        Snake *new_snake = new Snake(tmp);
        snakes.push_back(new_snake);
    }
    char tmp[30];
    sprintf(tmp, "%s/0.ga", filename);
    FILE *fp = fopen(tmp, "r");
    fscanf(fp, "%d", &t);
    for (unsigned i = 0; i < snakes.size(); i++)
        fscanf(fp, "%f%f%f%d", &snakes[i]->score, &snakes[i]->game_time, &snakes[i]->ann_time, &snakes[i]->age);
    fclose(fp);
    for (unsigned i = 0; i < snakes.size(); i++)
        snakes[i]->updateFinalScore();
}

// void Ga::reproduce(int num) {
//     for (int i = 0; i < num; i++) {
//         Snake *new_snake = new Snake;
//         new_snake->ann = snakes[i % snakes.size()]->ann;
//         new_snake->ann.variate(VARIATION_RATE);
//         snakes.push_back(new_snake);
//     }
// }

void Ga::reprodece(int id) {
    Snake *new_snake = new Snake;
    new_snake->ann = snakes[id]->ann;
    new_snake->ann.variateCell(CELL_VARIATION_RATE);
    new_snake->ann.variateEdge(EDGE_VARIATION_RATE);
    snakes.push_back(new_snake);
}

void Ga::reproduceAll(int num) {
    // 得到标准差、均分，及在均分之上的个体数
    float total_score = 0;
    for (unsigned i = 0; i < snakes.size(); i++)
        total_score += snakes[i]->final_score;
    float score_avg = total_score / snakes.size();
    int avg_num = 0;
    float tmp = 0;
    for (unsigned i = 0; i < snakes.size(); i++) {
        tmp += (snakes[i]->final_score - score_avg) * (snakes[i]->final_score - score_avg);
        if (snakes[i]->final_score > score_avg)
            avg_num++;
    }
    float score_standard_division = sqrt(tmp / snakes.size());

    // 记x=(分数-均分)/标准差，若分数满足正态分布，则对于正x,E(x)=1,E(x^2)=1.253
    int cnt = 0;
    for (int i = 0; i < avg_num; i++) {
        float x = (snakes[i]->final_score - score_avg) / score_standard_division;
        x = num*x*x/1.253314; // 1.253314=Integrate[x^2*Exp[x^2/2],{x,0,+Inf}]Integrate[x^2*Exp[x^2/2],{x,0,+Inf}]
        // 生成x个子代
        for (int j = 0; j < x; j++) {
            reprodece(i);
            if (++cnt == num)
                return;
        }
        // x不足1的部分用概率
        if (chance(x - (int)x)) {
            reprodece(i);
            cnt++;
        }
    }
    for (int i = 0; i < num - cnt; i++)
        reprodece(0);
}

void Ga::eliminateAll(int num) {
    for (int i = 0; i < num; i++) {
        delete *snakes.rbegin();
        snakes.pop_back();
    }
}

void Ga::evaluate(Snake *snake) {
    int total_score = 0, total_game_time = 0, total_ann_time = 0;
    int times;
    if (snake->age == 0)
        times = FIRST_EVALUATE_TIMES;
    else
        times = RE_EVALUATE_TIMES;
    for (int i = 0; i < times; i++) {
        snake->ann.clearMemory();
        Game game(4, 8, 2);
        for (int j = 0; j < MAX_GAME_TIME; j++) {
            bool data[32];
            game.outputData(data);
            int predict = snake->ann.run(data);
            if (predict == -1)
                break;
            int rst = game.go(predict);
            if (rst < 0)
                break;
        }
        total_score += game.score;
        total_game_time += game.time;
        total_ann_time += snake->ann.time;
    }
    snake->score = (snake->score * snake->age + total_score) / (snake->age + times);
    snake->game_time = (snake->game_time * snake->age + total_game_time) / (snake->age + times);
    snake->ann_time = (snake->ann_time * snake->age + total_ann_time) / (snake->age + times);
    snake->age += times;
    snake->updateFinalScore();
}

void Ga::evaluateAll() {
    #pragma omp parallel for
    for (unsigned i = 0; i < snakes.size(); i++)
        evaluate(snakes[i]);
    sort(snakes.begin(), snakes.end(), cmp);
}

void Ga::next() {
    t++;
    eliminateAll(snakes.size() - POPULATION_NUM * (1-EVOLUTION_RATE));
    reproduceAll(POPULATION_NUM - snakes.size());
    evaluateAll();
}

void Ga::saveAllParam(const char *filename) {
    char tmp[30];
    sprintf(tmp, "%s/0.ga", filename);
    FILE *fp = fopen(tmp, "w");
    fprintf(fp, "%d\n", t);
    for (unsigned i = 0; i < snakes.size(); i++)
        fprintf(fp, "%f %f %f %d\n", snakes[i]->score, snakes[i]->game_time, snakes[i]->ann_time, snakes[i]->age);
    fclose(fp);
    #pragma omp parallel for
    for (unsigned i = 0; i < snakes.size(); i++) {
        char tmp[30];
        sprintf(tmp, "%s/%d.snk", filename, i);
        snakes[i]->ann.saveParam(tmp);
    }
}

#endif